---
title: 单例模式
description: 单例设计模式（Singleton Design Pattern）是一种创建型设计模式，它确保一个类只有一个实例，并提供一个全局访问点。
sidebar_position: 1
---

## 单例模式的定义

单例设计模式（Singleton Design Pattern）是一种创建型设计模式，它确保一个类只允许创建一个对象（或者实例），并提供一个全局访问点来访问这个唯一实例。

![](https://wkq-img.oss-cn-chengdu.aliyuncs.com/site/20250708082333.png)
1. 保证一个类只有一个实例。
2. 为该实例提供一个全局访问点。
## 为什么使用单例模式？

单例模式主要用于以下两种场景：

### 1. 处理资源访问冲突

在多线程环境下，当多个线程同时访问同一个共享资源时，可能会发生资源访问冲突。使用单例模式可以确保对共享资源的访问是线程安全的。

例如，一个日志记录器（Logger）类，如果每个线程都创建自己的实例，可能会导致日志文件写入冲突：

```java
public class Logger {
    private FileWriter writer;
    private static final Logger instance = new Logger();

    private Logger() {
        try {
            File file = new File("log.txt");
            writer = new FileWriter(file, true); // true表示追加写入
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Logger getInstance() {
        return instance;
    }

    public void log(String message) {
        try {
            writer.write(message + "\n");
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
```

### 2. 表示全局唯一实例

某些类在系统中天然就应该只有一个实例，例如：

- 系统配置类：系统中只需要一份配置信息
- 线程池：系统级的线程池应该只有一个
- 计数器：用于生成唯一ID的计数器

例如，一个全局唯一的ID生成器：

```java
public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static final IdGenerator instance = new IdGenerator();

    private IdGenerator() {}

    public static IdGenerator getInstance() {
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}
```
```java title='springboot 示例'
//

初始化阶段：
 Spring容器创建 IdWorkerUtils Bean
 执行 init() 方法获取机器ID并保存实例到静态变量
ID生成阶段：
 外部调用 IdWorkerUtils.generateId()
 静态方法通过 idWorkerUtils 实例调用 nextId()
 nextId() 根据Snowflake算法生成唯一ID
//

package com.e6yun.project.tms.common.util;

import com.e6yun.project.common.redis.E6RedisService;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class IdWorkerUtils {
  @Resource
  private E6RedisService redisService;
  @Value("${spring.application.name:default}")
  private String appName;
  private static final Logger logger = LoggerFactory.getLogger(IdWorkerUtils.class);
  private static final long twepoch = 1489111610226L;
  private static final long workerIdBits = 10L;
  private final long maxWorkerId = 1023L;
  private static final long sequenceBits = 12L;
  private static final long workerIdShift = 12L;
  private static final long timestampLeftShift = 22L;
  private static final long sequenceMask = 4095L;
  private static long lastTimestamp = -1L;
  private long sequence = 0L;
  private long workerId;
  private static IdWorkerUtils idWorkerUtils;

  public IdWorkerUtils() {
  }

  @PostConstruct
  void init() {
    this.workerId = this.getMachineNum();
    if (this.workerId >= 0L && this.workerId <= 1023L) {
      logger.info("idWorkerUtils, workerId origin:{}", this.workerId);
      idWorkerUtils = this;
    } else {
      throw new RuntimeException(String.format("IdWorkerUtils worker Id can't be greater than %d or less than 0", 1023L));
    }
  }

  private long getMachineNum() {
    try {
      long increment = this.redisService.getStringRedisTemplate().opsForValue().increment(this.genFeatureIdKey(), 1L);
      return increment & 1023L;
    } catch (Exception var3) {
      Exception e = var3;
      throw new RuntimeException("IdWorkerUtils workerId increment error", e);
    }
  }

  String genFeatureIdKey() {
    return "snowflake-featureId".concat("-").concat(this.appName);
  }

  private synchronized long nextId() {
    long timestamp = this.timeGen();
    if (timestamp < lastTimestamp) {
      throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
    } else {
      if (lastTimestamp == timestamp) {
        this.sequence = this.sequence + 1L & 4095L;
        if (this.sequence == 0L) {
          timestamp = this.tilNextMillis(lastTimestamp);
        }
      } else {
        this.sequence = 0L;
      }

      lastTimestamp = timestamp;
      return timestamp - 1489111610226L << 22 | this.workerId << 12 | this.sequence;
    }
  }

  protected long tilNextMillis(long lastTimestamp) {
    long timestamp;
    for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
    }

    return timestamp;
  }

  protected long timeGen() {
    return System.currentTimeMillis();
  }

  public static synchronized String generateId() {
    return String.valueOf(idWorkerUtils.nextId());
  }
}

```


## 单例模式的实现方式

### 1. 饿汉式

特点：
- 在类加载时就完成了实例化
- 线程安全
- 不支持延迟加载，获取实例对象的速度快，但是如果对象比较大，而且一直没有使用的，就会造成内存的浪费 。

```java
public class Singleton {
    //2. 在本类中创建私有的静态全局对象， static 对象会随着类的加载而加载，且只有一次，由 JVM 保证
    private static final Singleton instance = new Singleton();
    //1. 私有构造方法
    private Singleton() {}
    //3. 提供一个全局访问点，供外部获取单例对象
    public static Singleton getInstance() {
        return instance;
    }
}
```

### 2. 懒汉式

特点：
- 支持延迟加载
- 线程安全但并发性能差，通过添加 synchronized 关键字实现
- 加锁导致性能开销大，获取实例函数的并发度很低

```java
public class Singleton {
    private static Singleton instance;
    //1. 私有构造方法
    private Singleton() {}
    //2. 提供一个全局访问点，供外部获取单例对象。通过判断是否被初始化，来选择是否创建对象。
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
```

### 3. 双重检测

特点：
- 支持延迟加载
- 线程安全且并发性能好
- 实现相对复杂

```java
public class Singleton {
    //2. 创建私有静态的全局对象
    private static volatile Singleton instance;
    //1. 私有构造方法
    private Singleton() {}
    //3. 获取单例对象的静态方法
    public static Singleton getInstance() {
        // 第一次判断，如果 instance 不为 null，不进入抢锁阶段，直接返回实例
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次判断，抢到锁之后，再次进行判断是否为 null 。
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
```

#### 为什么需要 volatile？

在双重检测模式中，使用 volatile 关键字是为了防止指令重排序导致的问题。创建一个对象实际上会执行以下三个步骤：

1. 分配内存空间
2. 初始化对象
3. 将引用指向分配的内存空间

由于 JVM 的指令重排优化，这三个步骤的实际执行顺序可能变成 1->3->2。如果是这样，在多线程环境下可能出现以下问题：

```java
// 线程A执行
memory = allocate();     // 1. 分配内存
instance = memory;       // 3. 设置instance指向内存空间
instance.init();        // 2. 初始化对象

// 线程B执行
if (instance != null) {   // 看到instance不为null，但实际对象还未初始化完成
    instance.doSomething(); // 访问未初始化完成的对象，可能导致异常
}
```

使用 volatile（保证对象可见性，屏蔽指令重排序） 关键字可以禁止这种重排序，它保证：

- 写操作不会和之前的操作重排序
- 读操作不会和之后的操作重排序
- 对象的发布对所有线程立即可见

### 4. 静态内部类

特点：
- 支持延迟加载
- 线程安全
- 实现简单


根据静态内部类的特性，同时解决了延时加载、线程安全的问题，并且代码更加简洁。
```java
public class Singleton {
    //1. 私有构造方法
    private Singleton() {}
    //2. 创建静态内部类
    private static class SingletonHolder {
        // 在静态内部类中创建单例，在加载内部类的时候才会创建单例对象，只会加载一次，由 JVM 保证线程安全。
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance; // 首次访问时加载内部类，创建单例对象
    }
}
```

### 5. 枚举

特点：
- 实现最简单
- 线程安全
- 防止反序列化创建新实例

```java
public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 业务方法
    }
}
```

:::tip
1. 如果不需要延迟加载，优先使用饿汉式
2. 如果需要延迟加载，推荐使用静态内部类方式
3. 如果涉及序列化，推荐使用枚举方式
4. 双重检测虽然是一个好的选择，但要注意 volatile 关键字的正确使用
:::
## 单例模式的问题

虽然单例模式使用广泛，但它也存在一些严重的问题，有些人甚至认为它是一种反模式（anti-pattern）。以下是主要问题：

### 1. 对 OOP 特性支持不友好

单例模式违背了面向对象的基本原则：

1. **违背基于接口而非实现的设计原则**：
```java
public class Order {
    public void create(...) {
        //...
        long id = IdGenerator.getInstance().getId();
        //...
    }
}

// 如果要针对不同业务使用不同ID生成器，需要大量修改代码
public class Order {
    public void create(...) {
        //...
        long id = OrderIdGenerator.getInstance().getId();
        //...
    }
}
```

2. **不利于继承和多态**：一旦将类设计为单例，就很难利用继承和多态来扩展其功能。

### 2. 隐藏类之间的依赖关系

- 单例模式通过静态方法调用，使得类之间的依赖关系不够明显
- 代码可读性差，需要仔细阅读代码才能发现类之间的依赖
- 不利于代码维护和重构

### 3. 影响代码扩展性

以数据库连接池为例：

```java
// 最初设计：单例的数据库连接池
public class DBConnectionPool {
    private static final DBConnectionPool instance = new DBConnectionPool();
    private DBConnectionPool() {}

    public static DBConnectionPool getInstance() {
        return instance;
    }
}

// 需求变更：需要两个连接池分别处理快慢SQL
// 但单例模式使得无法创建多个实例，需要重构整个类设计
```

所以，数据库连接池、线程池这类的资源池，最好还是不要设计成单例类。实际上，一些开源的数据库连接池、线程池也确实没有设计成单例类。

### 4. 不利于单元测试

1. **难以 Mock**：
```java
public class UserService {
    public void createUser(...) {
        // 直接使用单例，难以在测试时替换为mock对象
        long id = IdGenerator.getInstance().getId();
    }
}
```

2. **全局状态问题**：如果单例维护了状态，会影响不同测试用例之间的独立性

### 5. 构造函数参数传递问题

单例模式难以优雅地支持带参数的构造函数，虽然有以下几种解决方案，但都不够完美：

1. **通过 init 方法**：
```java
public class Singleton {
    private static Singleton instance = null;
    private final int paramA;
    private final int paramB;

    private Singleton(int paramA, int paramB) {
        this.paramA = paramA;
        this.paramB = paramB;
    }

    public synchronized static Singleton init(int paramA, int paramB) {
        if (instance != null) {
            throw new RuntimeException("Singleton has been created!");
        }
        instance = new Singleton(paramA, paramB);
        return instance;
    }
}
```

2. **通过配置类**：
```java
public class Config {
    public static final int PARAM_A = 123;
    public static final int PARAM_B = 245;
}

public class Singleton {
    private static Singleton instance = null;
    private final int paramA;
    private final int paramB;

    private Singleton() {
        this.paramA = Config.PARAM_A;
        this.paramB = Config.PARAM_B;
    }
}
```

## 替代方案

### 1. 依赖注入

将单例对象作为依赖注入到需要使用它的类中：

```java
// 原来的方式
public class UserService {
    public void createUser(...) {
        long id = IdGenerator.getInstance().getId();
    }
}

// 依赖注入方式
public class UserService {
    private final IdGenerator idGenerator;

    public UserService(IdGenerator idGenerator) {
        this.idGenerator = idGenerator;
    }

    public void createUser(...) {
        long id = idGenerator.getId();
    }
}
```

### 2. 使用 IOC 容器

利用 Spring 等 IOC 容器来管理对象的生命周期和依赖关系：

```java
@Component
public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }
}

@Service
public class UserService {
    @Autowired
    private IdGenerator idGenerator;

    public void createUser(...) {
        long id = idGenerator.getId();
    }
}
```

### 3. 工厂模式

使用工厂模式来控制对象的创建：

```java
public class IdGeneratorFactory {
    private static final Map<String, IdGenerator> generators = new ConcurrentHashMap<>();

    public static IdGenerator getGenerator(String type) {
        return generators.computeIfAbsent(type, k -> createGenerator(k));
    }

    private static IdGenerator createGenerator(String type) {
        // 根据类型创建不同的生成器
        return new IdGenerator();
    }
}
```

### 4. 静态方法

对于简单的场景，可以使用静态方法代替单例：

```java
public class IdGenerator {
    private static AtomicLong id = new AtomicLong(0);

    public static long getId() {
        return id.incrementAndGet();
    }
}
```

不过，静态方法这种实现思路，并不能解决之前提到的问题。

## 单例模式的作用范围

### 进程唯一

这是最常见的单例实现方式，单例对象在进程内是唯一的：

- 进程内唯一：同一个进程内只能有一个实例
- 进程间不唯一：不同进程可以各自创建实例
- 进程唯一意味着线程内、线程间都唯一

```java
public class ProcessSingleton {
    private static ProcessSingleton instance;

    private ProcessSingleton() {}

    public static synchronized ProcessSingleton getInstance() {
        if (instance == null) {
            instance = new ProcessSingleton();
        }
        return instance;
    }
}
```

### 线程唯一

线程唯一的单例表示：

- 线程内唯一：同一个线程内只能有一个实例
- 线程间可以不唯一：不同线程可以各自创建实例

实现方式有两种：

1. **使用HashMap存储**：

```java
public class ThreadSingleton {
    private static final ConcurrentHashMap<Long, ThreadSingleton> instances
            = new ConcurrentHashMap<>();

    private ThreadSingleton() {}

    public static ThreadSingleton getInstance() {
        Long currentThreadId = Thread.currentThread().getId();
        instances.putIfAbsent(currentThreadId, new ThreadSingleton());
        return instances.get(currentThreadId);
    }
}
```

2. **使用ThreadLocal工具类**：

```java
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> instance =
            new ThreadLocal<ThreadLocalSingleton>() {
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    private ThreadLocalSingleton() {}

    public static ThreadLocalSingleton getInstance() {
        return instance.get();
    }
}
```

### 集群唯一

集群唯一是最严格的单例实现：

- 进程内唯一
- 进程间也唯一
- 整个集群环境中只有一个实例

实现思路：

1. 将单例对象序列化到外部共享存储（如文件系统）
2. 使用分布式锁保证任意时刻只有一个进程可以访问对象
3. 使用完后需要释放锁并删除内存中的对象

示例实现：

```java
public class ClusterSingleton {
    private AtomicLong id = new AtomicLong(0);
    private static ClusterSingleton instance;
    private static SharedObjectStorage storage = new FileSharedObjectStorage();
    private static DistributedLock lock = new DistributedLock();

    private ClusterSingleton() {}

    public static synchronized ClusterSingleton getInstance() {
        if (instance == null) {
            lock.lock();
            instance = storage.load(ClusterSingleton.class);
        }
        return instance;
    }

    public synchronized void freeInstance() {
        storage.save(this, ClusterSingleton.class);
        instance = null; // 释放对象
        lock.unlock();
    }

    public long getId() {
        return id.incrementAndGet();
    }
}
```


```java title='序列化破坏单例问题解决'
package com.e6yun.project;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestSingletonSerializable {

  public static void main(String[] args) throws IOException, ClassNotFoundException {
     // 序列化对象输出流
    ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("tempFile.obj")));
    oos.writeObject(Singleton.getInstance());

    // 序列化对象输入流
    File file = new File("tempFile.obj");
    ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(file.toPath()));
    /**
     *
     */
    Singleton  singleton = (Singleton) ois.readObject();

    System.out.println( singleton); // com.e6yun.project.Singleton@27d6c5e0
    System.out.println(Singleton.getInstance()); //com.e6yun.project.Singleton@330bedb4
    System.out.println(Singleton.getInstance()==singleton); // false
  }
}

/**
 * 单例类实现序列化接口
 */
class Singleton implements Serializable {

  private static volatile Singleton singleton;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (singleton == null) {
      synchronized (Singleton.class) {
        if (singleton == null) {
          singleton = new Singleton();
        }
      }
    }
    return singleton;
  }

  /**
   * 只需要在单例类中定义 readResolve 方法，就可以解决序列化都单例的破坏
   * @return
   */
  private Object readResolve(){
    return singleton;
  }
}
```
### 多例模式

与单例模式相对的是多例模式，允许创建有限个数的实例。实现方式有两种：

1. **限制实例数量**：

```java
public class LimitedMultiton {
    private static final int MAX_NUM = 3;
    private static final Map<Integer, LimitedMultiton> instances = new HashMap<>();

    static {
        for(int i = 0; i < MAX_NUM; i++) {
            instances.put(i, new LimitedMultiton());
        }
    }

    private LimitedMultiton() {}

    public static LimitedMultiton getInstance(int index) {
        if(index < 0 || index >= MAX_NUM) {
            throw new IllegalArgumentException();
        }
        return instances.get(index);
    }
}
```

2. **基于类型的多例**：

```java
public class TypeMultiton {
    private static final ConcurrentHashMap<String, TypeMultiton> instances
            = new ConcurrentHashMap<>();

    private TypeMultiton() {}

    public static TypeMultiton getInstance(String type) {
        instances.putIfAbsent(type, new TypeMultiton());
        return instances.get(type);
    }
}
```

## 注意事项

1. 构造函数必须是私有的，防止外部直接创建实例
2. 考虑对象创建时的线程安全问题
3. 注意是否需要支持延迟加载
4. 考虑 getInstance() 的性能（是否需要加锁）
5. 如果单例类可能被序列化，需要特殊处理防止反序列化破坏单例
6. 在Java中，单例的唯一性实际上是基于类加载器（ClassLoader）的，而不是进程
7. 集群环境下的单例实现较为复杂，需要考虑：
   - 分布式锁的实现
   - 对象序列化和反序列化
   - 共享存储的可靠性
8. 多例模式在实际应用中较少使用，但在某些特定场景下很有用
9. 使用ThreadLocal实现线程唯一单例时要注意内存泄漏问题
10. 需要防止反射攻击，防止通过反射创建多个实例

